use std::num::NonZeroU16;

use super::super::cff::*;
use super::super::{DummyOutline, OutlineBuilder};
use super::*;

use outline::*;

/// A [Compact Font Format Table](
/// https://docs.microsoft.com/en-us/typography/opentype/spec/cff).
/// https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf page 13
#[derive(Debug, Clone)]
pub struct Table<'a> {
	pub major: Card8,
	pub minor: Card8,
	pub hdr_size: Card8,
	pub off_size: OffSize,

	pub name: Index<'a>,
	pub top: Index<'a>,
	pub top_dict: TopDict,
	pub strings: Index<'a>,
	pub global_subrs: Index<'a>,
	pub charset: Charset<'a>,
	pub number_of_glyphs: NonZeroU16,
	pub matrix: Matrix,
	pub char_strings: Index<'a>,
	pub kind: FontKind<'a>,

	pub table_data: &'a [u8],
}
impl<'a> FromSlice<'a> for Table<'a> {
	fn parse(data: &'a [u8]) -> Option<Self> {
		let mut s = Stream::new(data);
		let major = s.read()?;
		let minor = s.read()?;
		let hdr_size = s.read()?;
		let off_size = s.read()?;
		if major != 1 {
			return None;
		}
		s.advance(usize::from(hdr_size).checked_sub(s.offset())?);

		let name = Index::parse::<Card16>(&mut s)?;

		let top = Index::parse::<Card16>(&mut s)?;
		let top_dict = TopDict::parse(top.get(0)?)?;
		// Must be set, otherwise there are nothing to parse.
		if top_dict.char_strings_offset == 0 {
			return None;
		}

		// String INDEX.
		let strings = Index::parse::<Card16>(&mut s)?;

		// Parse Global Subroutines INDEX.
		let global_subrs = Index::parse::<Card16>(&mut s)?;

		let char_strings = {
			let mut s = Stream::new_at(data, top_dict.char_strings_offset)?;
			Index::parse::<Card16>(&mut s)?
		};

		// 'The number of glyphs is the value of the count field in the CharStrings INDEX.'
		let number_of_glyphs = u16::try_from(char_strings.len()).ok().and_then(NonZeroU16::new)?;

		let charset = match top_dict.charset_offset {
			Some(charset_id::ISO_ADOBE) => Charset::ISOAdobe,
			Some(charset_id::EXPERT) => Charset::Expert,
			Some(charset_id::EXPERT_SUBSET) => Charset::ExpertSubset,
			Some(offset) => {
				let mut s = Stream::new_at(data, offset)?;
				parse_charset(number_of_glyphs, &mut s)?
			}
			None => Charset::ISOAdobe, // default
		};

		let matrix = top_dict.matrix;

		let kind = if top_dict.has_ros {
			Self::parse_cid_metadata(data, top_dict.clone(), number_of_glyphs.get())?
		} else {
			// Only SID fonts are allowed to have an Encoding.
			let encoding = match top_dict.encoding_offset {
				Some(encoding_id::STANDARD) => Encoding::new_standard(),
				Some(encoding_id::EXPERT) => Encoding::new_expert(),
				Some(offset) => parse_encoding(&mut Stream::new_at(data, offset)?)?,
				None => Encoding::new_standard(), // default
			};
			Self::parse_sid_metadata(data, top_dict.clone(), encoding)?
		};

		Some(Self {
			major,
			minor,
			hdr_size,
			off_size,
			name,
			top,
			top_dict,
			strings,
			global_subrs,
			charset,
			number_of_glyphs,
			matrix,
			char_strings,
			kind,
			table_data: data,
		})
	}
}
impl<'a> Table<'a> {
	fn parse_cid_metadata(data: &'_ [u8], top_dict: TopDict, number_of_glyphs: u16) -> Option<FontKind<'_>> {
		let (charset_offset, fd_array_offset, fd_select_offset) =
			match (top_dict.charset_offset, top_dict.fd_array_offset, top_dict.fd_select_offset) {
				(Some(a), Some(b), Some(c)) => (a, b, c),
				_ => return None, // charset, FDArray and FDSelect must be set.
			};

		if charset_offset <= charset_id::EXPERT_SUBSET {
			// 'There are no predefined charsets for CID fonts.'
			// Adobe Technical Note #5176, chapter 18 CID-keyed Fonts
			return None;
		}

		let mut metadata = CIDMetadata::default();

		metadata.fd_array = {
			let mut s = Stream::new_at(data, fd_array_offset)?;
			Index::parse::<Card16>(&mut s)?
		};

		metadata.fd_select = {
			let mut s = Stream::new_at(data, fd_select_offset)?;
			FDSelect::parse(number_of_glyphs, &mut s)?
		};

		Some(FontKind::CID(metadata))
	}

	fn parse_sid_metadata(data: &'a [u8], top_dict: TopDict, encoding: Encoding<'a>) -> Option<FontKind<'a>> {
		let mut metadata = SIDMetadata::default();
		metadata.encoding = encoding;

		let private_dict = if let Some(range) = top_dict.private_dict_range.clone() {
			PrivateDict::parse(data.get(range)?)
		} else {
			return Some(FontKind::SID(metadata));
		};

		metadata.default_width = private_dict.default_width.unwrap_or(0.0);
		metadata.nominal_width = private_dict.nominal_width.unwrap_or(0.0);

		if let (Some(private_dict_range), Some(subroutines_offset)) = (top_dict.private_dict_range, private_dict.local_subroutines_offset) {
			// 'The local subroutines offset is relative to the beginning
			// of the Private DICT data.'
			if let Some(start) = private_dict_range.start.checked_add(subroutines_offset) {
				let data = data.get(start..data.len())?;
				let mut s = Stream::new(data);
				metadata.local_subrs = Index::parse::<Card16>(&mut s)?;
			}
		}

		Some(FontKind::SID(metadata))
	}
}
impl<'a> Table<'a> {
	/// Outlines a glyph.
	pub fn outline(&self, glyph_id: GlyphId, builder: &mut dyn OutlineBuilder) -> Result<Rect, CFFError> {
		let data = self.char_strings.get(u32::from(glyph_id.0)).ok_or(CFFError::NoGlyph)?;
		parse_char_string(data, self, glyph_id, false, builder).map(|v| v.0)
	}
	/// Returns a glyph width.
	///
	/// This value is different from outline bbox width and is stored separately.
	///
	/// Technically similar to [`Face::glyph_hor_advance`](crate::Face::glyph_hor_advance).
	pub fn glyph_width(&self, glyph_id: GlyphId) -> Option<u16> {
		match self.kind {
			FontKind::SID(ref sid) => {
				let data = self.char_strings.get(u32::from(glyph_id.0))?;
				let (_, width) = parse_char_string(data, self, glyph_id, true, &mut DummyOutline).ok()?;
				let width = width.map(|w| sid.nominal_width + w).unwrap_or(sid.default_width);
				u16::try_from(width as i32).ok()
			}
			FontKind::CID(_) => None,
		}
	}

	/// Resolves a Glyph ID for a code point.
	///
	/// Similar to [`Face::glyph_index`](crate::Face::glyph_index) but 8bit
	/// and uses CFF encoding and charset tables instead of TrueType `cmap`.
	pub fn glyph_index(&self, code_point: u8) -> Option<GlyphId> {
		match self.kind {
			FontKind::SID(ref sid_meta) => {
				match sid_meta.encoding.code_to_gid(&self.charset, code_point) {
					Some(id) => Some(id),
					None => {
						// Try using the Standard encoding otherwise.
						// Custom Encodings does not guarantee to include all glyphs.
						Encoding::new_standard().code_to_gid(&self.charset, code_point)
					}
				}
			}
			FontKind::CID(_) => None,
		}
	}
	/// Returns the CID corresponding to a glyph ID.
	///
	/// Returns `None` if this is not a CIDFont.
	pub fn glyph_cid(&self, glyph_id: GlyphId) -> Option<u16> {
		match self.kind {
			FontKind::SID(_) => None,
			FontKind::CID(_) => self.charset.gid_to_sid(glyph_id).map(|id| id.0),
		}
	}

	/// Returns a glyph ID by a name.
	pub fn glyph_index_by_name(&self, name: &str) -> Option<GlyphId> {
		match self.kind {
			FontKind::SID(_) => {
				let sid = if let Some(index) = STANDARD_NAMES.iter().position(|n| *n == name) {
					StringId(index as u16)
				} else {
					let index = self.strings.into_iter().position(|n| n == name.as_bytes())?;
					StringId((STANDARD_NAMES.len() + index) as u16)
				};

				self.charset.sid_to_gid(sid)
			}
			FontKind::CID(_) => None,
		}
	}
	/// Returns a glyph name.
	pub fn glyph_name(&self, glyph_id: GlyphId) -> Option<&'a str> {
		match self.kind {
			FontKind::SID(_) => {
				let sid = self.charset.gid_to_sid(glyph_id)?;
				let sid = usize::from(sid.0);
				match STANDARD_NAMES.get(sid) {
					Some(name) => Some(name),
					None => {
						let idx = u32::try_from(sid - STANDARD_NAMES.len()).ok()?;
						let name = self.strings.get(idx)?;
						core::str::from_utf8(name).ok()
					}
				}
			}
			FontKind::CID(_) => None,
		}
	}
}

#[derive(Clone, Copy, Debug)]
pub enum FontKind<'a> {
	SID(SIDMetadata<'a>),
	CID(CIDMetadata<'a>),
}

#[derive(Clone, Copy, Default, Debug)]
pub struct SIDMetadata<'a> {
	pub local_subrs: Index<'a>,
	/// Can be zero.
	pub default_width: f32,
	/// Can be zero.
	pub nominal_width: f32,
	pub encoding: Encoding<'a>,
}

#[derive(Clone, Copy, Default, Debug)]
pub struct CIDMetadata<'a> {
	pub fd_array: Index<'a>,
	pub fd_select: FDSelect<'a>,
}

#[derive(Clone, Copy, Debug)]
pub enum FDSelect<'a> {
	Format0(LazyArray16<'a, u8>),
	Format3(&'a [u8]), // It's easier to parse it in-place.
}
impl Default for FDSelect<'_> {
	fn default() -> Self {
		FDSelect::Format0(LazyArray16::default())
	}
}
impl FDSelect<'_> {
	pub fn parse<'a>(number_of_glyphs: u16, s: &mut Stream<'a>) -> Option<FDSelect<'a>> {
		let format = s.read::<u8>()?;
		match format {
			0 => Some(FDSelect::Format0(s.read_array16::<u8>(number_of_glyphs)?)),
			3 => Some(FDSelect::Format3(s.tail()?)),
			_ => None,
		}
	}
	pub fn font_dict_index(&self, glyph_id: GlyphId) -> Option<u8> {
		match self {
			FDSelect::Format0(ref array) => array.get(glyph_id.0),
			FDSelect::Format3(data) => {
				let mut s = Stream::new(data);
				let number_of_ranges = s.read::<u16>()?;
				if number_of_ranges == 0 {
					return None;
				}

				// 'A sentinel GID follows the last range element and serves
				// to delimit the last range in the array.'
				// So we can simply increase the number of ranges by one.
				let number_of_ranges = number_of_ranges.checked_add(1)?;

				// Range is: GlyphId + u8
				let mut prev_first_glyph = s.read::<GlyphId>()?;
				let mut prev_index = s.read::<u8>()?;
				for _ in 1..number_of_ranges {
					let curr_first_glyph = s.read::<GlyphId>()?;
					if (prev_first_glyph..curr_first_glyph).contains(&glyph_id) {
						return Some(prev_index);
					} else {
						prev_index = s.read::<u8>()?;
					}

					prev_first_glyph = curr_first_glyph;
				}

				None
			}
		}
	}
}

mod outline {
	use super::*;

	const MAX_ARGUMENTS_STACK_LEN: usize = 48;
	const STACK_LIMIT: u8 = 10;
	const TWO_BYTE_OPERATOR_MARK: u8 = 12;

	/// Enumerates some operators defined in the Adobe Technical Note #5177.
	mod operator {
		pub const HORIZONTAL_STEM: u8 = 1;
		pub const VERTICAL_STEM: u8 = 3;
		pub const VERTICAL_MOVE_TO: u8 = 4;
		pub const LINE_TO: u8 = 5;
		pub const HORIZONTAL_LINE_TO: u8 = 6;
		pub const VERTICAL_LINE_TO: u8 = 7;
		pub const CURVE_TO: u8 = 8;
		pub const CALL_LOCAL_SUBROUTINE: u8 = 10;
		pub const RETURN: u8 = 11;
		pub const ENDCHAR: u8 = 14;
		pub const HORIZONTAL_STEM_HINT_MASK: u8 = 18;
		pub const HINT_MASK: u8 = 19;
		pub const COUNTER_MASK: u8 = 20;
		pub const MOVE_TO: u8 = 21;
		pub const HORIZONTAL_MOVE_TO: u8 = 22;
		pub const VERTICAL_STEM_HINT_MASK: u8 = 23;
		pub const CURVE_LINE: u8 = 24;
		pub const LINE_CURVE: u8 = 25;
		pub const VV_CURVE_TO: u8 = 26;
		pub const HH_CURVE_TO: u8 = 27;
		pub const SHORT_INT: u8 = 28;
		pub const CALL_GLOBAL_SUBROUTINE: u8 = 29;
		pub const VH_CURVE_TO: u8 = 30;
		pub const HV_CURVE_TO: u8 = 31;
		pub const HFLEX: u8 = 34;
		pub const FLEX: u8 = 35;
		pub const HFLEX1: u8 = 36;
		pub const FLEX1: u8 = 37;
		pub const FIXED_16_16: u8 = 255;
	}

	struct CharStringParserContext<'a> {
		metadata: &'a Table<'a>,
		width: Option<f32>,
		stems_len: u32,
		has_endchar: bool,
		has_seac: bool,
		glyph_id: GlyphId, // Required to parse local subroutine in CID fonts.
		local_subrs: Option<Index<'a>>,
	}

	pub fn parse_char_string(
		data: &[u8],
		metadata: &Table,
		glyph_id: GlyphId,
		width_only: bool,
		builder: &mut dyn OutlineBuilder,
	) -> Result<(Rect, Option<f32>), CFFError> {
		let local_subrs = match metadata.kind {
			FontKind::SID(ref sid) => Some(sid.local_subrs),
			FontKind::CID(_) => None, // Will be resolved on request.
		};

		let mut ctx = CharStringParserContext {
			metadata,
			width: None,
			stems_len: 0,
			has_endchar: false,
			has_seac: false,
			glyph_id,
			local_subrs,
		};

		let mut inner_builder = Builder {
			builder,
			bbox: RectF::new(),
		};

		let stack = ArgumentsStack {
			data: &mut [0.0; MAX_ARGUMENTS_STACK_LEN], // 192B
			len: 0,
			max_len: MAX_ARGUMENTS_STACK_LEN,
		};
		let mut parser = CharStringParser {
			stack,
			builder: &mut inner_builder,
			x: 0.0,
			y: 0.0,
			has_move_to: false,
			is_first_move_to: true,
			width_only,
		};
		_parse_char_string(&mut ctx, data, 0, &mut parser)?;

		if width_only {
			return Ok((Rect::zero(), ctx.width));
		}

		if !ctx.has_endchar {
			return Err(CFFError::MissingEndChar);
		}

		let bbox = parser.builder.bbox;

		// Check that bbox was changed.
		if bbox.is_default() {
			return Err(CFFError::ZeroBBox);
		}

		let rect = bbox.to_rect().ok_or(CFFError::BboxOverflow)?;
		Ok((rect, ctx.width))
	}

	fn _parse_char_string(
		ctx: &mut CharStringParserContext,
		char_string: &[u8],
		depth: u8,
		p: &mut CharStringParser,
	) -> Result<(), CFFError> {
		let mut s = Stream::new(char_string);
		while !s.at_end() {
			let op = s.read::<u8>().ok_or(CFFError::ReadOutOfBounds)?;
			match op {
				0 | 2 | 9 | 13 | 15 | 16 | 17 => {
					// Reserved.
					return Err(CFFError::InvalidOperator);
				}
				operator::HORIZONTAL_STEM
				| operator::VERTICAL_STEM
				| operator::HORIZONTAL_STEM_HINT_MASK
				| operator::VERTICAL_STEM_HINT_MASK => {
					// y dy {dya dyb}* hstem
					// x dx {dxa dxb}* vstem
					// y dy {dya dyb}* hstemhm
					// x dx {dxa dxb}* vstemhm

					// If the stack length is uneven, than the first value is a `width`.
					let len = if p.stack.len().is_odd() && ctx.width.is_none() {
						ctx.width = Some(p.stack.at(0));
						p.stack.len() - 1
					} else {
						p.stack.len()
					};

					ctx.stems_len += len as u32 >> 1;

					// We are ignoring the hint operators.
					p.stack.clear();
				}
				operator::VERTICAL_MOVE_TO => {
					let mut i = 0;
					if p.stack.len() == 2 {
						i += 1;
						if ctx.width.is_none() {
							ctx.width = Some(p.stack.at(0));
						}
					}

					p.parse_vertical_move_to(i)?;
				}
				operator::LINE_TO => {
					p.parse_line_to()?;
				}
				operator::HORIZONTAL_LINE_TO => {
					p.parse_horizontal_line_to()?;
				}
				operator::VERTICAL_LINE_TO => {
					p.parse_vertical_line_to()?;
				}
				operator::CURVE_TO => {
					p.parse_curve_to()?;
				}
				operator::CALL_LOCAL_SUBROUTINE => {
					if p.stack.is_empty() {
						return Err(CFFError::InvalidArgumentsStackLength);
					}

					if depth == STACK_LIMIT {
						return Err(CFFError::NestingLimitReached);
					}

					// Parse and remember the local subroutine for the current glyph.
					// Since it's a pretty complex task, we're doing it only when
					// a local subroutine is actually requested by the glyphs charstring.
					if ctx.local_subrs.is_none() {
						if let FontKind::CID(ref cid) = ctx.metadata.kind {
							ctx.local_subrs = parse_cid_local_subrs(ctx.metadata.table_data, ctx.glyph_id, cid);
						}
					}

					if let Some(local_subrs) = ctx.local_subrs {
						let subroutine_bias = calc_subroutine_bias(local_subrs.len());
						let index = conv_subroutine_index(p.stack.pop(), subroutine_bias)?;
						let char_string = local_subrs.get(index).ok_or(CFFError::InvalidSubroutineIndex)?;
						_parse_char_string(ctx, char_string, depth + 1, p)?;
					} else {
						return Err(CFFError::NoLocalSubroutines);
					}

					if ctx.has_endchar && !ctx.has_seac {
						if !s.at_end() {
							return Err(CFFError::DataAfterEndChar);
						}

						break;
					}
				}
				operator::RETURN => {
					break;
				}
				TWO_BYTE_OPERATOR_MARK => {
					// flex
					let op2 = s.read::<u8>().ok_or(CFFError::ReadOutOfBounds)?;
					match op2 {
						operator::HFLEX => p.parse_hflex()?,
						operator::FLEX => p.parse_flex()?,
						operator::HFLEX1 => p.parse_hflex1()?,
						operator::FLEX1 => p.parse_flex1()?,
						_ => return Err(CFFError::UnsupportedOperator),
					}
				}
				operator::ENDCHAR => {
					if p.stack.len() == 4 || (ctx.width.is_none() && p.stack.len() == 5) {
						// Process 'seac'.
						let accent_char = seac_code_to_glyph_id(&ctx.metadata.charset, p.stack.pop()).ok_or(CFFError::InvalidSeacCode)?;
						let base_char = seac_code_to_glyph_id(&ctx.metadata.charset, p.stack.pop()).ok_or(CFFError::InvalidSeacCode)?;
						let dy = p.stack.pop();
						let dx = p.stack.pop();

						if ctx.width.is_none() && !p.stack.is_empty() {
							ctx.width = Some(p.stack.pop())
						}

						ctx.has_seac = true;

						if depth == STACK_LIMIT {
							return Err(CFFError::NestingLimitReached);
						}

						let base_char_string = ctx
							.metadata
							.char_strings
							.get(u32::from(base_char.0))
							.ok_or(CFFError::InvalidSeacCode)?;
						_parse_char_string(ctx, base_char_string, depth + 1, p)?;
						p.x = dx;
						p.y = dy;

						let accent_char_string = ctx
							.metadata
							.char_strings
							.get(u32::from(accent_char.0))
							.ok_or(CFFError::InvalidSeacCode)?;
						_parse_char_string(ctx, accent_char_string, depth + 1, p)?;
					} else if p.stack.len() == 1 && ctx.width.is_none() {
						ctx.width = Some(p.stack.pop());
					}

					if !p.is_first_move_to {
						p.is_first_move_to = true;
						p.builder.close();
					}

					if !s.at_end() {
						return Err(CFFError::DataAfterEndChar);
					}

					ctx.has_endchar = true;

					break;
				}
				operator::HINT_MASK | operator::COUNTER_MASK => {
					let mut len = p.stack.len();

					// We are ignoring the hint operators.
					p.stack.clear();

					// If the stack length is uneven, than the first value is a `width`.
					if len.is_odd() {
						len -= 1;
						if ctx.width.is_none() {
							ctx.width = Some(p.stack.at(0));
						}
					}

					ctx.stems_len += len as u32 >> 1;

					s.advance(usize::num_from((ctx.stems_len + 7) >> 3));
				}
				operator::MOVE_TO => {
					let mut i = 0;
					if p.stack.len() == 3 {
						i += 1;
						if ctx.width.is_none() {
							ctx.width = Some(p.stack.at(0));
						}
					}

					p.parse_move_to(i)?;
				}
				operator::HORIZONTAL_MOVE_TO => {
					let mut i = 0;
					if p.stack.len() == 2 {
						i += 1;
						if ctx.width.is_none() {
							ctx.width = Some(p.stack.at(0));
						}
					}

					p.parse_horizontal_move_to(i)?;
				}
				operator::CURVE_LINE => {
					p.parse_curve_line()?;
				}
				operator::LINE_CURVE => {
					p.parse_line_curve()?;
				}
				operator::VV_CURVE_TO => {
					p.parse_vv_curve_to()?;
				}
				operator::HH_CURVE_TO => {
					p.parse_hh_curve_to()?;
				}
				operator::SHORT_INT => {
					let n = s.read::<i16>().ok_or(CFFError::ReadOutOfBounds)?;
					p.stack.push(f32::from(n))?;
				}
				operator::CALL_GLOBAL_SUBROUTINE => {
					if p.stack.is_empty() {
						return Err(CFFError::InvalidArgumentsStackLength);
					}

					if depth == STACK_LIMIT {
						return Err(CFFError::NestingLimitReached);
					}

					let subroutine_bias = calc_subroutine_bias(ctx.metadata.global_subrs.len());
					let index = conv_subroutine_index(p.stack.pop(), subroutine_bias)?;
					let char_string = ctx.metadata.global_subrs.get(index).ok_or(CFFError::InvalidSubroutineIndex)?;
					_parse_char_string(ctx, char_string, depth + 1, p)?;

					if ctx.has_endchar && !ctx.has_seac {
						if !s.at_end() {
							return Err(CFFError::DataAfterEndChar);
						}

						break;
					}
				}
				operator::VH_CURVE_TO => {
					p.parse_vh_curve_to()?;
				}
				operator::HV_CURVE_TO => {
					p.parse_hv_curve_to()?;
				}
				32..=246 => {
					p.parse_int1(op)?;
				}
				247..=250 => {
					p.parse_int2(op, &mut s)?;
				}
				251..=254 => {
					p.parse_int3(op, &mut s)?;
				}
				operator::FIXED_16_16 => {
					p.parse_fixed(&mut s)?;
				}
			}

			if p.width_only && ctx.width.is_some() {
				break;
			}
		}

		// TODO: 'A charstring subroutine must end with either an endchar or a return operator.'

		Ok(())
	}

	/// In CID fonts, to get local subroutines we have to:
	///   1. Find Font DICT index via FDSelect by GID.
	///   2. Get Font DICT data from FDArray using this index.
	///   3. Get a Private DICT offset from a Font DICT.
	///   4. Get a local subroutine offset from Private DICT.
	///   5. Parse a local subroutine at offset.
	fn parse_cid_local_subrs<'a>(data: &'a [u8], glyph_id: GlyphId, cid: &CIDMetadata) -> Option<Index<'a>> {
		let font_dict_index = cid.fd_select.font_dict_index(glyph_id)?;
		let font_dict_data = cid.fd_array.get(u32::from(font_dict_index))?;
		let private_dict_range = parse_font_dict(font_dict_data)?;
		let private_dict_data = data.get(private_dict_range.clone())?;
		let private_dict = PrivateDict::parse(private_dict_data);
		let subroutines_offset = private_dict.local_subroutines_offset?;

		// 'The local subroutines offset is relative to the beginning
		// of the Private DICT data.'
		let start = private_dict_range.start.checked_add(subroutines_offset)?;
		let subrs_data = data.get(start..)?;
		let mut s = Stream::new(subrs_data);
		Index::parse::<u16>(&mut s)
	}

	fn seac_code_to_glyph_id(charset: &Charset, n: f32) -> Option<GlyphId> {
		let code = u8::try_num_from(n)?;

		let sid = STANDARD_ENCODING[usize::from(code)];
		let sid = StringId(u16::from(sid));

		match charset {
			Charset::ISOAdobe => {
				// ISO Adobe charset only defines string ids up to 228 (zcaron)
				if code <= 228 {
					Some(GlyphId(sid.0))
				} else {
					None
				}
			}
			Charset::Expert | Charset::ExpertSubset => None,
			_ => charset.sid_to_gid(sid),
		}
	}
}
